package com.jack.coustomlargeimageview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Scroller;

import androidx.annotation.Nullable;

import java.io.IOException;
import java.io.InputStream;

public class LoadLargeView extends View implements View.OnTouchListener, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {

    //区域解码器
    private BitmapRegionDecoder mDecode;

    private BitmapFactory.Options mOptions;

    //图片的宽度
    private int mImageWidth;

    //图片的高度
    private int mImageHeight;

    //创建了一个矩阵对象 Matrix，用于处理图像的缩放和平移操作
    Matrix matrix = new Matrix();

    //图片缩放因子
    private float mScale;

    //控件的宽度
    private int mViewWidth;

    //控件的高度
    private int mViewHeight;

    //需要显示的区域
    private Rect mRect;

    //缩放因子初始值
    private float mOriginalScale;

    //需要展示的图片，是被复用的
    private Bitmap mBitmap;

    //捕获用户的手势
    private GestureDetector mGestureDetector;

    //缩放功能
    private ScaleGestureDetector mScaleGestureDetector;

    //滑动帮助类
    private Scroller mScroller;

    //缩放
    public LoadLargeView(Context context) {
        super(context);
    }

    public LoadLargeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mRect = new Rect();
        mOptions = new BitmapFactory.Options();
        mGestureDetector = new GestureDetector(context, this);
        mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGesture());
        mScroller = new Scroller(context);
        setOnTouchListener(this);
    }

    // 处理缩放的回调事件 view  view  正确 不正确 2  缩略图
    class ScaleGesture extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            // 获取当前的缩放因子
            float scale = mScale;
            // 计算新的缩放因子，通过减去1来使得 detector.getScaleFactor() 的值从0开始
            scale += detector.getScaleFactor() - 1;
            //放大因子的原始放大系数
            // 限制缩放因子的范围在 [originalScale, originalScale * 2]
            if (scale <= mOriginalScale) {
                scale = mOriginalScale;
            } else if (scale > mOriginalScale * 2) {
                scale = mOriginalScale * 2;
            }
            // 更新滑动区域的右侧和底部位置，以及当前的缩放因子
            mRect.right = mRect.left + (int) (mViewWidth / scale);
            mRect.bottom = mRect.top + (int) (mViewHeight / scale);
            mScale = scale;
            invalidate();
            // 返回父类的 onScale 方法的结果
            //这一行通常用于在需要扩展 ScaleGesture 类的同时保留父类的默认行为。
            return super.onScale(detector);
        }
    }

    public void setImage(InputStream is) {
        //设置为 true 时，该属性将只解析图像的边界信息而不加载图像数据到内存中
        mOptions.inJustDecodeBounds = true;
        //        不加载像素
        //作用1 解码位图： 该方法将输入流中的图像数据解码成一个位图对象。解码后的位图可以用于显示在UI上，保存到文件中，或者进行其他操作
        //作用3 处理 outPadding 对应参数2
        //作用2 配置解码参数： 通过传递 BitmapFactory.Options 对象，可以配置解码的行为。例如，通过设置 inSampleSize 可以减小解码后的位图的分辨率，以减少内存消耗。
        //通过配置 BitmapFactory.Options 对象，你可以对解码过程进行各种控制，以满足应用程序的需求。
        BitmapFactory.decodeStream(is, null, mOptions);

        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;
        //开启复用内存
        mOptions.inMutable = true;
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        //        mOptions.inJustDecodeBounds = false;
        try {
            //BitmapRegionDecoder.newInstance(InputStream is, boolean shareable) 方法用于创建 BitmapRegionDecoder 对象，
            //它允许你解码图像的局部区域而不需要解码整个图像。这对于处理大图或者需要在用户交互时逐步加载图像的情况非常有用。
            //作用1：局部解码
            //通过合理使用这个类，可以在保持性能的同时有效地处理大图
            mDecode = BitmapRegionDecoder.newInstance(is, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //刷新 调用 requestLayout() 方法会触发 View 树的重新布局过程
        //可能会导致以下方法的执行 onMeasure onLayout onSizeChanged onDraw
        //        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = Math.min(mImageWidth, mViewWidth);
        mRect.bottom = Math.min(mImageHeight, mViewHeight);
        // 再定义一个缩放因子
        mOriginalScale = mViewWidth / (float) mImageWidth;
        mScale = mOriginalScale;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (null == mDecode) {
            return;
        }
        //将 mBitmap 设置给 mOptions 的 inBitmap 属性。这是一个尝试在加载新的位图时重用已有内存的操作，以减少内存的消耗。如果成功，新的位图将会使用这块内存；如果不能重用，会返回 null。
        mOptions.inBitmap = mBitmap;
        //使用缩放因子 mScale 设置矩阵 matrix 的缩放系数，以便后续的位图绘制操作可以按照指定的缩放比例进行绘制。
        //设置方法系数
        matrix.setScale(mScale, mScale);
        //使用 BitmapRegionDecoder 对象 mDecode，根据指定的区域 mRect 和配置参数 mOptions 解码局部区域的图像。解码后的结果赋值给 mBitmap。
        //这里的mBitmap是区域解码之后的
        mBitmap = mDecode.decodeRegion(mRect, mOptions);
        //将解码后的位图 mBitmap 使用矩阵 matrix 进行绘制到画布 canvas 上。这是实际的绘制操作，将图像按照指定的缩放因子和矩阵进行绘制。
        canvas.drawBitmap(mBitmap, matrix, null);
    }

    /**
     * onTouch 方法用于处理触摸事件，通过调用 GestureDetector 和 ScaleGestureDetector 的 onTouchEvent 方法，
     * 处理普通手势和缩放手势。最后，返回 true 表示事件已被处理。
     */
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        //调用 mGestureDetector 对象的 onTouchEvent 方法，传递当前的 MotionEvent。这是用于处理普通手势的 GestureDetector，例如单击、双击等。
        mGestureDetector.onTouchEvent(motionEvent);
        //调用 mScaleGestureDetector 对象的 onTouchEvent 方法，传递当前的 MotionEvent。这是用于处理缩放手势的 ScaleGestureDetector。
        mScaleGestureDetector.onTouchEvent(motionEvent);
        //表示事件已经被处理，不再传递给其他监听器。这里返回 true 表示 onTouch 方法已经处理了触摸事件，不需要其他监听器再进行处理。如果返回 false，则表示事件未被消费，可能会继续传递给其他监听器。
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean onDoubleTap(MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean onDown(MotionEvent motionEvent) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent motionEvent) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent motionEvent) {
        return false;
    }

    /**
     * GestureDetector.OnGestureListener 接口的一部分，用于处理滚动手势。它会在用户进行滑动操作时被调用
     */
    @Override
    public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float distanceX, float distanceY) {
        //根据手指在水平和垂直方向上的滑动距离 (distanceX 和 distanceY)，更新 mRect 对象的位置。这行代码将 mRect 在屏幕上进行偏移，实现图像的拖动效果。
        mRect.offset((int) distanceX, (int) distanceY);

        // 边界值判断
        //如果拖动导致滑动区域底部超过图像的底部边界 (mImageHeight)，则将底部边界限制为图像的底部，同时调整顶部边界，确保显示的区域高度为 mViewHeight / mScale。
        if (mRect.bottom > mImageHeight) {
            mRect.bottom = mImageHeight;
            mRect.top = mImageHeight - (int) (mViewHeight / mScale);
        }
        if (mRect.top < 0) {
            mRect.top = 0;
            mRect.bottom = (int) (mViewHeight / mScale);
        }

        //如果拖动导致滑动区域左侧超过图像的左侧边界 (小于0)，则将左侧边界限制为0，同时调整右侧边界，确保显示的区域宽度为 mViewWidth / mScale。
        if (mRect.left < 0) {
            mRect.left = 0;
            mRect.right = (int) (mViewWidth / mScale);
        }
        if (mRect.right > mImageWidth) {
            mRect.right = mImageWidth;
            mRect.left = mImageWidth - (int) (mViewWidth / mScale);
        }
        invalidate();
        return false;
    }

    @Override
    public void onLongPress(MotionEvent motionEvent) {

    }

    /**
     * 是 GestureDetector.OnGestureListener 接口中的一个方法，用于处理快速滑动（fling）手势。在触发了一次快速滑动手势时，系统会回调此方法，允许开发者处理相应的逻辑。
     */
    @Override
    public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float velocityX, float velocityY) {
        //调用 mScroller 对象的 fling 方法，使用惯性滚动的方式计算最终滚动位置。
        //参数5：在这里设置为0，意味着横向滑动的最小位置为0，即不允许向左滑动超过图像的左边缘。这是因为横向滑动位置小于0的情况表示超出了图像的左边缘。
        mScroller.fling(mRect.left, mRect.top, (int) velocityX, -(int) velocityY, 0,
                mImageWidth - (int) (mViewWidth / mScale), 0, mImageHeight - (int) (mViewHeight / mScale));
        //返回 false，表示该事件未被消费。这里的 onFling 方法通常不会返回 true，因为 onFling 的处理是为了通过 mScroller 实现惯性滚动，而不是消费触摸事件。
        return false;
    }

    /**
     * 用于处理滚动的计算和更新
     * 如果滚动动画尚未完成，它会根据 mScroller 的计算结果更新滚动位置，并请求重新绘制视图，以呈现动画效果。这是实现滑动效果的关键部分，通常与 Scroller 类一起使用，用于平滑地滚动和处理惯性滑动。
     * <p>
     * 该方法在很多个情况下会被触发调用
     */
    @Override
    public void computeScroll() {
        super.computeScroll();

        if (mScroller.isFinished()) {
            return;
        }
        //调用 mScroller 的 computeScrollOffset 方法，判断是否需要继续计算滚动。如果返回 true，说明滚动位置还在更新中，需要继续处理。
        if (mScroller.computeScrollOffset()) {
            // 更新滚动位置
            mRect.top = mScroller.getCurrY();
            mRect.bottom = mRect.top + (int) (mViewHeight / mScale);

            invalidate();
        }
    }
}
